feat(http): refactor node:http client instrumentation for portability#20393
feat(http): refactor node:http client instrumentation for portability#20393
Conversation
1f9cef2 to
5ab332a
Compare
size-limit report 📦
|
18b93f6 to
fd04ed5
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
0877c6e to
1963717
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
1963717 to
3e7001c
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
0307e3a to
640f8f0
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
1b8377c to
1a8d51e
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
645dc0e to
e88e5f5
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
e88e5f5 to
ddd2987
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ddd2987. Configure here.
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
24f03e6 to
eac6329
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
aacfec1 to
edb38de
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
edb38de to
0c11611
Compare
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
0c11611 to
76df64d
Compare
| @@ -0,0 +1,86 @@ | |||
| import { createTestServer } from '@sentry-internal/test-utils'; | |||
There was a problem hiding this comment.
Extremely nice that there are tests for this scenario. Maybe it makes sense to display a warning in our docs too: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/http/ (not sure if this is the best place to put it)
| const baggageEntry = `${encodeURIComponent(objectKey)}=${encodeURIComponent(objectValue)}`; | ||
| const newBaggageHeader = currentIndex === 0 ? baggageEntry : `${baggageHeader},${baggageEntry}`; | ||
| if (newBaggageHeader.length > MAX_BAGGAGE_STRING_LENGTH) { | ||
| /* v8 ignore start */ |
There was a problem hiding this comment.
q: Why exactly is this needed?
There was a problem hiding this comment.
There's a test verifying that it hit the limit, but I didn't feel like it was essential to mock out the debugging and verify the log, so the ignore just marks it as "not tested, but it's fine". This gets the function to full coverage, while marking that we're cheating a bit to do so. Arguably this is a bit lazy, but the v8 ignore comment sort of acts as a TODO.
This was implemented for the portable Express integration, but others will need the same functionality, so make it a reusable util.
Refactor the `node:http` outgoing request instrumentation so that it can be applied to non-Node.js environments by patching the http module. Also, refactor so that the diagnostics_channel and monkeypatching paths can share code, and so that light and normal node-core instrumentations can share more of the functionality as well. To facilitate this, some portable minimal types are vendored in from the `node:http` module.
| if (sentryTrace && !request.getHeader('sentry-trace')) { | ||
| try { | ||
| request.setHeader('sentry-trace', sentryTrace); | ||
| DEBUG_BUILD && debug.log(LOG_PREFIX, 'Added sentry-trace header'); | ||
| } catch (e) { | ||
| DEBUG_BUILD && | ||
| debug.error(LOG_PREFIX, 'Failed to set sentry-trace header:', isError(e) ? e.message : 'Unknown error'); | ||
| } | ||
| } | ||
|
|
||
| if (traceparent && !request.getHeader('traceparent')) { | ||
| try { | ||
| request.setHeader('traceparent', traceparent); | ||
| DEBUG_BUILD && debug.log(LOG_PREFIX, 'Added traceparent header'); | ||
| } catch (e) { | ||
| DEBUG_BUILD && | ||
| debug.error(LOG_PREFIX, 'Failed to set traceparent header:', isError(e) ? e.message : 'Unknown error'); | ||
| } | ||
| } | ||
|
|
||
| if (baggage) { | ||
| const merged = mergeBaggageHeaders(request.getHeader('baggage'), baggage); | ||
| if (merged) { | ||
| try { | ||
| request.setHeader('baggage', merged); | ||
| DEBUG_BUILD && debug.log(LOG_PREFIX, 'Added baggage header'); | ||
| } catch (e) { | ||
| DEBUG_BUILD && | ||
| debug.error(LOG_PREFIX, 'Failed to set baggage header:', isError(e) ? e.message : 'Unknown error'); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
m: On develop we currently skip setting traceparent and modifying baggage if the request already has a sentry-trace. In this refactoring we only skip overwriting sentry-trace.
See:
sentry-javascript/packages/node-core/src/utils/outgoingHttpRequest.ts
Lines 66 to 117 in d58f82a
And in fetch:
There was a problem hiding this comment.
Hm, yeah, I think one of the clankers complained about this. In this file, I opted to just keep it as it was already in the code that I was porting, but it's probably a good time to update and make it consistent. Easy enough to update, good catch!
| if (getCurrentScope().getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] === true) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
l: The behavior here changed slightly, on develop we first check for suppression and only then check the ignoreOutgoingRequests hook. So users could now potentially observe these despite being marked for suppressed.
Nothing critical here, just a small deviation.
There was a problem hiding this comment.
Hm, that's a good point, it is also slightly less efficient to run a userland function than to just check a flag.
| ? '' | ||
| : `:${requestOptions.port}`; | ||
| const path = requestOptions.path ? requestOptions.path : '/'; | ||
| return `${protocol}//${hostname}${port}${path}`; |
There was a problem hiding this comment.
m: Since breadcrumbs now also use this helper, I found out that there's a slight deviation from develop specifically for breadcrumbs when requestOptions.path is already an absolute path. We'd end up with something like http://proxy.localhttp://example.com/foo if the request looked like this:
const req = http.request({
host: 'proxy.local',
port: 3128,
method: 'GET',
path: 'http://target.example/foo',
});Whereas on develop, we would get just http://target.example/foo.
Now, the interesting part is that spans, ignore callbacks, trace propagation etc were all going through this same logic on develop already, so if this is an issue, it was already an issue before.
Breadcrumbs seems to be the only path that did this correctly, maybe we can align on that instead?
| } | ||
| const { outgoingRequestApplyCustomAttributes: applyCustomAttributesOnSpan, ...options } = this.getConfig(); | ||
| const patchOptions: HttpInstrumentationOptions = { | ||
| propagateTrace: options.propagateTraceInOutgoingRequests, |
There was a problem hiding this comment.
q/l: Do options contain propagateTrace? Wonder if the spreading further down might overwrite it. If not, disregard.
| expect(request.setHeader).not.toHaveBeenCalledWith('traceparent', expect.anything()); | ||
| }); | ||
|
|
||
| it('merges baggage with existing baggage header', () => { |
There was a problem hiding this comment.
m: I think we need another test here to check this does not happen when sentry-trace is already present (regression from what's on develop, see other comment).

Refactor the
node:httpoutgoing request instrumentation so that it canbe applied to non-Node.js environments by patching the http module.
Also, refactor so that the diagnostics_channel and monkeypatching paths
can share code, and so that light and normal node-core instrumentations
can share more of the functionality as well.
To facilitate this, some portable minimal types are vendored in from the
node:httpmodule.yarn lint) & (yarn test).Notes on test changes:
conditionalTeston a node version.originspan data changes from the vague (and now incorrect)auto.http.otel.httptoauto.http.clientfor client spans. This also affects a lot of the tests.Otherwise, all prior behavior should be unchanged, which is reflected in the integration and unit tests.